#ifndef AVALON_QUEUEDSIGNALHANDLER_HPP
#define AVALON_QUEUEDSIGNALHANDLER_HPP
#include <vector>
#include <boost/enable_shared_from_this.hpp>
#include <boost/preprocessor/repetition/enum_params.hpp>
#include <boost/preprocessor/repetition/enum_binary_params.hpp>
#include <boost/preprocessor/repetition/repeat.hpp>
#include <boost/preprocessor/iteration/local.hpp>
#include <boost/signals2.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/type_traits/remove_pointer.hpp>
#include <boost/type_traits/remove_reference.hpp>
#include "Avalon/SignalHandling/SignalHandling.hpp"

namespace Avalon {
namespace SignalHandling {

  /*! \class QueuedSignalHandler
   *  \brief Allows for the delayed handling of signals.
   *  \author Kamal Mansouri <kranar@hotmail.com>
   */
  class QueuedSignalHandler :
      public boost::enable_shared_from_this<QueuedSignalHandler> {
    public:

      //! Indicates that one or more signals have been queued.
      typedef boost::signals2::signal<void ()> QueuedSignal;

      //! Creates a QueuedSignalHandler.
      static QueuedSignalHandlerPtr Create();

      ~QueuedSignalHandler();

      //! Dispatches all signals.
      void HandleSignals();

      //! Returns a slot compatible with this signal handler.
      /*!
        \param slot The slot to make compatible with this signal handler.
        \return A slot compatible with this signal handler.
      */
      template<typename Signal>
      typename Signal::slot_type GetSlot(
          const typename Signal::slot_type& slot) {
        return GetSlotImplementation<Signal>::Invoke(this, slot).track(
          shared_from_this());
      }

      //! Connects to the QueuedSignal.
      /*!
        \param slot The slot to connect.
        \param isQueued [out] <code>true</code> iff signals have been queued.
        \return The connection to the QueuedSignal.
      */
      boost::signals2::connection ConnectQueuedSignal(
        const QueuedSignal::slot_type& slot, bool* isQueued = NULL);

    private:
      class Callback;
      boost::mutex m_mutex;
      std::vector<Callback*> m_slots;
      QueuedSignal m_queuedSignal;

      QueuedSignalHandler();
      void Slot(const boost::signals2::signal<void ()>::slot_type& slot);

      template<typename Signal, int Arity = Signal::arity>
      struct GetSlotImplementation {};

      template<typename Signal>
      struct GetSlotImplementation<Signal, 0> {
        static typename Signal::slot_type Invoke(QueuedSignalHandler* handler,
            const typename Signal::slot_type& slot) {
          return boost::bind(static_cast<void (QueuedSignalHandler::*)(
            const boost::signals2::signal<void ()>::slot_type&)>(
            &QueuedSignalHandler::Slot), handler, slot);
        }
      };

      class Callback {
        public:
          virtual ~Callback();

          virtual void Invoke() = 0;
      };

      class CallbackZero : public Callback {
        public:
          CallbackZero(const boost::signals2::signal<void ()>::slot_type& slot);

          virtual ~CallbackZero();

          virtual void Invoke();

        private:
          boost::signals2::signal<void ()>::slot_type m_slot;
      };

      #define AVALON_QUEUED_SIGNAL_HANDLER_PARAMETER_COUNT 10

      #define AVALON_QUEUED_SIGNAL_HANDLER_PARAMETERS(z, n, a)                 \
        BOOST_PP_COMMA_IF(n) a ## n

      #define AVALON_QUEUED_SIGNAL_HANDLER_TYPE(z, n, a)                       \
        BOOST_PP_COMMA_IF(n) typename Signal::template arg<n>::type

      #define AVALON_QUEUED_SIGNAL_HANDLER_TUPLE_MEMBERS(z, n, a)              \
        BOOST_PP_COMMA_IF(n) typename boost::remove_reference<a##n>::type

      #define AVALON_QUEUED_SIGNAL_HANDLER_PLACEHOLDERS(z, n, a)               \
        BOOST_PP_COMMA_IF(n) boost::arg<n + 1>()

      #define AVALON_QUEUED_SIGNAL_HANDLER_GET_TUPLE(z, n, a)                  \
        BOOST_PP_COMMA_IF(n) boost::get<n>(a)

      #define BOOST_PP_LOCAL_MACRO(n)                                          \
      template<BOOST_PP_ENUM_PARAMS(n, typename A)>                            \
      class Callback##n : public Callback {                                    \
        public:                                                                \
                                                                               \
          Callback##n(typename boost::signals2::signal<                        \
              void (BOOST_PP_ENUM_PARAMS(n, A))>::slot_type& slot,             \
              BOOST_PP_ENUM_BINARY_PARAMS(n, A, a))                            \
              : m_slot(slot),                                                  \
                m_parameters(BOOST_PP_REPEAT(n,                                \
                  AVALON_QUEUED_SIGNAL_HANDLER_PARAMETERS, a)) {}              \
                                                                               \
          virtual void Invoke() {                                              \
            m_slot(BOOST_PP_REPEAT(n, AVALON_QUEUED_SIGNAL_HANDLER_GET_TUPLE,  \
              m_parameters));                                                  \
          }                                                                    \
                                                                               \
        private:                                                               \
          typename boost::signals2::signal<                                    \
            void (BOOST_PP_ENUM_PARAMS(n, A))>::slot_type m_slot;              \
          boost::tuple<BOOST_PP_REPEAT(n,                                      \
            AVALON_QUEUED_SIGNAL_HANDLER_TUPLE_MEMBERS, A)> m_parameters;      \
      };                                                                       \
                                                                               \
      template<BOOST_PP_ENUM_PARAMS(n, typename A)>                            \
      void Slot(typename boost::signals2::signal<                              \
          void (BOOST_PP_ENUM_PARAMS(n, A))>::slot_type& slot,                 \
          BOOST_PP_ENUM_BINARY_PARAMS(n, A, a)) {                              \
        boost::lock_guard<boost::mutex> lock(m_mutex);                         \
        m_slots.push_back(                                                     \
          new Callback##n<BOOST_PP_ENUM_PARAMS(n, A)>(slot,                    \
          BOOST_PP_REPEAT(n, AVALON_QUEUED_SIGNAL_HANDLER_PARAMETERS, a)));    \
        if(m_slots.size() == 1) {                                              \
          m_queuedSignal();                                                    \
        }                                                                      \
      }                                                                        \
                                                                               \
      template<typename Signal>                                                \
      struct GetSlotImplementation<Signal, n> {                                \
        static typename Signal::slot_type Invoke(QueuedSignalHandler* handler, \
            const typename Signal::slot_type& slot) {                          \
          return boost::bind(&QueuedSignalHandler::Slot<BOOST_PP_REPEAT(n,     \
            AVALON_QUEUED_SIGNAL_HANDLER_TYPE, a)>, handler, slot,             \
            BOOST_PP_REPEAT(n, AVALON_QUEUED_SIGNAL_HANDLER_PLACEHOLDERS, a)); \
        }                                                                      \
      };

      #define BOOST_PP_LOCAL_LIMITS                                            \
        (1, AVALON_QUEUED_SIGNAL_HANDLER_PARAMETER_COUNT)
      #include BOOST_PP_LOCAL_ITERATE()
  };
}
}

#endif // AVALON_QUEUEDSIGNALHANDLER_HPP
